package onlinebanking.security.probabilistic;

import java.math.BigInteger;
import java.security.SecureRandom;

import onlinebanking.math.GoldwasserMicali;

/**
 * 
 * @author Edgar
 * 
 */
public class GMKeyGenerator {

	private static GMKeyGenerator instance = new GMKeyGenerator();

	private final static int DEFAULT_BITLENGTH = 8;
	private final static int CERTAINTY = 14;

	private SecureRandom secureRandom;
	private GoldwasserMicali gm;

	private GMKeyGenerator() {
		this.secureRandom = new SecureRandom();
		this.gm = new GoldwasserMicali();
	}

	public static synchronized GMKeyGenerator getInstance() {
		return GMKeyGenerator.instance;
	}

	public GMPrivateKey generatePrivateKey(int bitLength) {
		BigInteger p = new BigInteger(bitLength, GMKeyGenerator.CERTAINTY,
				this.secureRandom);
		BigInteger q = new BigInteger(bitLength, GMKeyGenerator.CERTAINTY,
				this.secureRandom);

		return wrapToGMPrivateKey(p, q);
	}

	public GMPrivateKey generatePrivateKey() {
		return this.generatePrivateKey(GMKeyGenerator.DEFAULT_BITLENGTH);
	}

	public GMPublicKey generateGMPublicKeyFromPrivateKey(
			GMPrivateKey gmPrivateKey) {
		BigInteger p = gmPrivateKey.getP();
		BigInteger q = gmPrivateKey.getQ();

		BigInteger modulus = p.multiply(q);
		BigInteger pseudoSquare = gm.findPseudosquare(p, q);

		return wrapToGMPublicKey(modulus, pseudoSquare);
	}

	public GMKeyPair generateKeyPair(int bitLength) {

		final GMPrivateKey gmPrivateKey = this.generatePrivateKey(bitLength);
		final GMPublicKey gmPublicKey = this
				.generateGMPublicKeyFromPrivateKey(gmPrivateKey);

		return this.wrapToKeyPair(gmPrivateKey, gmPublicKey);
	}

	public GMKeyPair generateKeyPair() {
		return this.generateKeyPair(GMKeyGenerator.DEFAULT_BITLENGTH);
	}

	public GMKeyPair wrapToKeyPair(final GMPrivateKey gmPrivateKey,
			final GMPublicKey gmPublicKey) {
		return new GMKeyPair() {
			@Override
			public GMPrivateKey getPrivate() {
				return gmPrivateKey;
			}

			@Override
			public GMPublicKey getPublic() {
				return gmPublicKey;
			}

		};
	}

	private GMPrivateKey wrapToGMPrivateKey(final BigInteger p,
			final BigInteger q) {
		return new GMPrivateKey() {
			@Override
			public BigInteger getP() {
				return p;
			}

			@Override
			public BigInteger getQ() {
				return q;
			}
		};
	}

	private GMPublicKey wrapToGMPublicKey(final BigInteger modulus,
			final BigInteger pseudoSquare) {
		return new GMPublicKey() {
			@Override
			public BigInteger getModulus() {
				return modulus;
			}

			@Override
			public BigInteger getPseudoSquare() {
				return pseudoSquare;
			}
		};
	}

}
